
/*****************************************************************************\
 *                Interactive test for collision algorithms
\*****************************************************************************/

/*! @file
 *
 *  @brief
 *    Test collision algorithms interactively
 *
 *  Two identical objects are created.
 *  The user can move the camera, and one of the objects.
 *  With every motion, a collision check is performed.
 *
 *  Type './interactive -h' for an explanation of the command line options
 *  and keys.
 *
 *  Keys: (interactive version)
 *  - i     : polygon mode (wireframe, dots, filled)
 *  - m     : switch between moving the camera or the object
 *  - space : go to the next test case
 *
 *  @author  Gabriel Zachmann
 *	- reimplemented by Weiyu Yi, wyi@tu-clausthal.de
 *
 *  @todo
 *  - Farbe fuer DOP tree Visualisierung.
 *  - Can handle only triangles currently!
 *
 *  @flags
 */


//---------------------------------------------------------------------------
//  Includes
//---------------------------------------------------------------------------
#include <Collision.h>
#include <ColBoxtree.h>
#include <ColDopTree.h>
#include <ColTopology.h>
#include <ColUtils.h>
#include "ColUtilsOpenGL.h"

using col::Point3;
using col::Point3f;
using col::ColGeometry;
using col::Primitive;
using col::Cube;
using col::Icosahedron;
using col::Trackball;

using col::Quat;
using col::Vector3;
using col::Vector4;
using col::Matrix4;
using col::Boxtree;
using col::DopTree;

namespace
{

static const unsigned int MaxNumCol = 50000;

typedef enum
{
	PERSPECTIVE,
	ORTHOGRAPHIC	
} ProjectionE;

typedef enum
{
	MODELING,
	VIEWING
} ControlModeE;


// here declaration of the global variables.
bool with_windows = true;
ColGeometry * fixed_geo = NULL;
ColGeometry * moving_geo = NULL;
ColGeometry * col_geo_1 = NULL;
ColGeometry * col_geo_2 = NULL;
Boxtree  * fixed_boxtree = NULL;
Boxtree  * moving_boxtree = NULL;
DopTree  * fixed_doptree = NULL;
DopTree  * moving_doptree = NULL;

// Col. variables
col::AlgoE algo = col::ALGO_DOPTREE;
Matrix4 fixed_matrix;
Matrix4 moving_matrix;
bool moved = true;

//vector<const Pnt3Array *> fixed_points;
//vector<const Pnt3Array *> moving_points;
vector< const std::vector< Point3f> *> fixed_points;
vector< const std::vector< Point3f> *> moving_points;

// Col.  options
bool all_polygons = true;

// render options
GLuint w, h;
ControlModeE ctrol = VIEWING;
ProjectionE proj = PERSPECTIVE;
GLenum fill_mode = GL_LINE;

Trackball tball;
GLdouble view_distance = 3.0;
Matrix4 viewing_matrix;

GLdouble fixed_color[3] = { 0.5, 0.5, 0.7 };
GLdouble moving_color[3] = { 0.5, 0.5, 0.7 };
GLdouble col_color_1[3] = { 1.0, 0.0, 0.0 };
GLdouble col_color_2[3] = { 0.0, 1.0, 0.0 };

// data for visualizing the collision
Point3 tot_pnt[2][MaxNumCol*5];
unsigned int tot_face[2][MaxNumCol*5];
unsigned int tot_face_nv[2][MaxNumCol];
unsigned int tot_no_nvertices[2] = { 0, 0 };
unsigned int tot_no_faces_nv[2] = { 0, 0 };

}


/**  Help Function for Visualisation
 *
 * @param node,points,pgon,nvertices Geometry of the intersecting polyogn
 * @param nr    index of colliding osg-node
 *
 **/
void setColVisData ( const col::Matrix4 *mat, 
					 const col::Point3f *points,
                     const unsigned int *pgon, 
					 const unsigned int nvertices, 
					 int nr )
{
    for ( unsigned int i=0; i < nvertices; i++ )
	{
		Vector4 v4 = (*mat) * points[pgon[i]];
		tot_pnt[nr][i + tot_no_nvertices[nr]][0] = v4[0];
		tot_pnt[nr][i + tot_no_nvertices[nr]][1] = v4[1];
		tot_pnt[nr][i + tot_no_nvertices[nr]][2] = v4[2];

		tot_face[nr][i + tot_no_nvertices[nr]] = i + tot_no_nvertices[nr];
	}

    tot_no_nvertices[nr] += nvertices;
    tot_face_nv[nr][tot_no_faces_nv[nr]] = nvertices;
    tot_no_faces_nv[nr]++;
}

/*****************************************************************************\
 *                         check_coll_tree
\*****************************************************************************/

void check_coll_tree( void )
{
try
{
	if ( moved == false )
	{
		return;
	}

	// clear the old collision record
	if ( tot_no_faces_nv[0] > 0 )
	{
		tot_no_nvertices[0] = 0;
		tot_no_faces_nv[0] = 0;

		tot_no_nvertices[1] = 0;
		tot_no_faces_nv[1] = 0;
	}

	if ( col_geo_1 != NULL ) delete col_geo_1;
	col_geo_1 = new ColGeometry();
	if ( col_geo_2 != NULL ) delete col_geo_2;
	col_geo_2 = new ColGeometry();

	// collision test begins
	Matrix4 m12 = inverse( moving_matrix );
	col::Data data( fixed_geo, moving_geo );
	data.all_polygons = all_polygons;
	data.m12 = m12;
	
	bool coll = false;
	if ( algo == col::ALGO_DOPTREE )
	{
		coll = fixed_doptree->check( *moving_doptree, &data );
	}
	else
	{
		coll = fixed_boxtree->check( *moving_boxtree, fixed_geo, moving_geo, &data );
	}

    if ( coll )
    {
		printf( "!%5u colliding polygon pairs collected !\n", data.polisecdata.size() );
        for ( std::vector<col::PolygonIntersectionData>::const_iterator it = data.polisecdata.begin(); it != data.polisecdata.end(); it++ )
		{
			setColVisData( &fixed_matrix, (*it).points[0],
					(*it).pgon[0], (*it).nvertices[0], 0 );

			setColVisData( &moving_matrix, (*it).points[1],
					(*it).pgon[1], (*it).nvertices[1], 1 );

		}

		col::buildGeometryFromPoints( col_geo_1, tot_pnt[0], tot_no_nvertices[0],
				tot_face[0], tot_face_nv[0], tot_no_faces_nv[0] );

		col::buildGeometryFromPoints( col_geo_2, tot_pnt[1], tot_no_nvertices[1],
				tot_face[1], tot_face_nv[1], tot_no_faces_nv[1] );

	}
	else
	{
		printf("!   no collision detected !\n" );
	}

	moved = false;
	return;
}
catch ( col::XCollision &x )
{
	x.print(stderr);
	exit(-1);					// not sensible to continue testing
}
}

/*****************************************************************************\
 *                             testcase
\*****************************************************************************/

/// phase *must* start from 0 and increase step-wise by 1!
void testcase( int * phase )
{
try
{
	printf("\nphase %d\n", *phase );
	switch ( *phase ) 	
	{
		case 0:
			if ( fixed_geo != NULL ) { delete fixed_geo; fixed_geo = NULL; }
			fixed_geo = new ColGeometry();
			col::makeCube( fixed_geo, 0.5 );
			
			if ( moving_geo != NULL ) { delete moving_geo; moving_geo = NULL; }
			moving_geo = new ColGeometry();
			col::makeCube( moving_geo, 0.5 );

			if ( algo == col::ALGO_DOPTREE )
			{
				if ( fixed_doptree != NULL ) { delete fixed_doptree; fixed_doptree = NULL; }
				fixed_doptree = new DopTree( fixed_geo, fixed_points );
				if ( moving_doptree != NULL ) { delete moving_doptree; moving_doptree = NULL; }
				moving_doptree = new DopTree( moving_geo, moving_points );
			} else
			{
				if ( fixed_boxtree != NULL ) { delete fixed_boxtree; fixed_boxtree = NULL; }
				fixed_boxtree = new Boxtree( fixed_geo, fixed_points );
				if ( moving_boxtree != NULL ) { delete moving_boxtree; moving_boxtree = NULL; }
				moving_boxtree = new Boxtree( moving_geo, fixed_points );
			}
			
			break;
		case 1:
			if ( fixed_geo != NULL ) { delete fixed_geo; fixed_geo = NULL; }
			fixed_geo = new ColGeometry();
			col::makeSphere( fixed_geo, 1, 1.0 );
			
			if ( moving_geo != NULL ) { delete moving_geo; moving_geo = NULL; }
			moving_geo = new ColGeometry();
			col::makeSphere( moving_geo, 1, 1.0 );

			if ( algo == col::ALGO_DOPTREE )
			{
				if ( fixed_doptree != NULL ) { delete fixed_doptree; fixed_doptree = NULL; }
				fixed_doptree = new DopTree( fixed_geo, fixed_points );
				if ( moving_doptree != NULL ) { delete moving_doptree; moving_doptree = NULL; }
				moving_doptree = new DopTree( moving_geo, moving_points );
			} else
			{
				if ( fixed_boxtree != NULL ) { delete fixed_boxtree; fixed_boxtree = NULL; }
				fixed_boxtree = new Boxtree( fixed_geo, fixed_points );
				if ( moving_boxtree != NULL ) { delete moving_boxtree; moving_boxtree = NULL; }
				moving_boxtree = new Boxtree( moving_geo, fixed_points );
			}

			break;
		case 2:
			if ( fixed_geo != NULL ) { delete fixed_geo; fixed_geo = NULL; }
			fixed_geo = new ColGeometry();
			col::makeSphere( fixed_geo, 0, 1.0 );

			if ( moving_geo != NULL ) { delete moving_geo; moving_geo = NULL; }
			moving_geo = new ColGeometry();
			col::makeSphere( moving_geo, 0, 1.0 );

			if ( algo == col::ALGO_DOPTREE )
			{
				if ( fixed_doptree != NULL ) { delete fixed_doptree; fixed_doptree = NULL; }
				fixed_doptree = new DopTree( fixed_geo, fixed_points );
				if ( moving_doptree != NULL ) { delete moving_doptree; moving_doptree = NULL; }
				moving_doptree = new DopTree( moving_geo, moving_points );
			} else
			{
				if ( fixed_boxtree != NULL ) { delete fixed_boxtree; fixed_boxtree = NULL; }
				fixed_boxtree = new Boxtree( fixed_geo, fixed_points );
				if ( moving_boxtree != NULL ) { delete moving_boxtree; moving_boxtree = NULL; }
				moving_boxtree = new Boxtree( moving_geo, fixed_points );
			}

			break;
		case 3: // Load Geometry
		{
			if ( fixed_geo != NULL ) { delete fixed_geo; fixed_geo = NULL; }
			fixed_geo = new ColGeometry();
            std::string filepath( FILE_LOAD_PATH );
            std::string filename( "pin.obj" );
            col::loadGeometryFromFile( fixed_geo, ( filepath + filename ).c_str() );
			//col::loadGeometryFromFile( fixed_geo, "../../../coll/wrapper/wrapper_opengl/test/Data/pin.obj" );

			if ( moving_geo != NULL ) { delete moving_geo; moving_geo = NULL; }
			moving_geo = new ColGeometry();
            std::string filepath1( FILE_LOAD_PATH );
            std::string filename1( "pin.obj" );
            col::loadGeometryFromFile( moving_geo, ( filepath1 + filename1 ).c_str() );
			//col::loadGeometryFromFile( moving_geo, "../../../coll/wrapper/wrapper_opengl/test/Data/pin.obj" );

			if ( algo == col::ALGO_DOPTREE )
			{
				if ( fixed_doptree != NULL ) { delete fixed_doptree; fixed_doptree = NULL; }
				fixed_doptree = new DopTree( fixed_geo, fixed_points );
				if ( moving_doptree != NULL ) { delete moving_doptree; moving_doptree = NULL; }
				moving_doptree = new DopTree( moving_geo, moving_points );
			} else
			{
				if ( fixed_boxtree != NULL ) { delete fixed_boxtree; fixed_boxtree = NULL; }
				fixed_boxtree = new Boxtree( fixed_geo, fixed_points );
				if ( moving_boxtree != NULL ) { delete moving_boxtree; moving_boxtree = NULL; }
				moving_boxtree = new Boxtree( moving_geo, fixed_points );
			}

			break;
		}
		case 4: // Load Geometry
		{
			if ( fixed_geo != NULL ) { delete fixed_geo; fixed_geo = NULL; }
			fixed_geo = new ColGeometry();
            std::string filepath( FILE_LOAD_PATH );
            std::string filename( "ball.obj" );
            col::loadGeometryFromFile( fixed_geo, ( filepath + filename ).c_str() );
			//col::loadGeometryFromFile( fixed_geo, "../../../coll/wrapper/wrapper_opengl/test/Data/ball.obj" );

			if ( moving_geo != NULL ) { delete moving_geo; moving_geo = NULL; }
			moving_geo = new ColGeometry();
            std::string filepath1( FILE_LOAD_PATH );
            std::string filename1( "ball.obj" );
            col::loadGeometryFromFile( moving_geo, ( filepath1 + filename1 ).c_str() );
			//col::loadGeometryFromFile( moving_geo, "../../../coll/wrapper/wrapper_opengl/test/Data/ball.obj" );

			if ( algo == col::ALGO_DOPTREE )
			{
				if ( fixed_doptree != NULL ) { delete fixed_doptree; fixed_doptree = NULL; }
				fixed_doptree = new DopTree( fixed_geo, fixed_points );
				if ( moving_doptree != NULL ) { delete moving_doptree; moving_doptree = NULL; }
				moving_doptree = new DopTree( moving_geo, moving_points );
			} else
			{
				if ( fixed_boxtree != NULL ) { delete fixed_boxtree; fixed_boxtree = NULL; }
				fixed_boxtree = new Boxtree( fixed_geo, fixed_points );
				if ( moving_boxtree != NULL ) { delete moving_boxtree; moving_boxtree = NULL; }
				moving_boxtree = new Boxtree( moving_geo, fixed_points );
			}

			break;
		}
		default:
			printf( "last phase has been reached; starting again.\n" );
			*phase = -1;
			break;
	}
}
catch ( col::XCollision &x )
{
	x.print(stderr);
	exit(-1);					// not sensible to continue testing
}
}

void init(void) 
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_FLAT);
	glEnable(GL_DEPTH_TEST);
}

void reshape( int width, int height )
{
	w = width;
	h = height;
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
	tball.setViewport( w, h );
}

void display(void)
{
	if ( fixed_geo == NULL || moving_geo == NULL )
	{
		glutSwapBuffers();
		return;
	}
	
	// trackball 
	if ( ctrol == VIEWING )
	{
		if ( tball.getTrackMode() == col::TRACK_ROTATE )
		{
			REAL angle;
			REAL axis[3];
			tball.getRotation( axis, angle );
			Matrix4 m = Matrix4::rotation( angle, col::normalize( col::Vector3( axis[0], axis[1], axis[2] ) ) );
			viewing_matrix = m * viewing_matrix;
		}
		else
		if ( tball.getTrackMode() == col::TRACK_ZOOM )
		{
			REAL zoom;
			tball.getZoom( zoom );
			Matrix4 m = Matrix4::scale( col::Vector3( zoom+1 ) );
			viewing_matrix = m * viewing_matrix;
		}
	}
	else
	if ( ctrol == MODELING )
	{
		if ( tball.getTrackMode() == col::TRACK_ROTATE )
		{
			REAL axis[3];
			REAL angle;
			tball.getRotation( axis, angle );
			Matrix4 m = Matrix4::rotation( angle, col::normalize( col::Vector3( axis[0], axis[1], axis[2] ) ) );
			moving_matrix = moving_matrix * m;
		}
		else
		if ( tball.getTrackMode() == col::TRACK_ZOOM )
		{
			REAL trans;
			tball.getZoom( trans );
			Matrix4 m = Matrix4::translation( col::Vector3( trans/10.0, 0.0, 0.0 ) );
			moving_matrix = m * moving_matrix;
		}
	}
	
	// Call Collision Model
	check_coll_tree();

	// Till here all geometries are ready to render
	// --------------------------------------------------------------------------------
	
	// --------------------------------------------------------------------------------
	// display begins
	
	// Prjection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity ();

	GLdouble range=2.0;
	switch( proj )
	{
		case ORTHOGRAPHIC:
			// orthographic projection
			if (w <= h)
				glOrtho(-1*range, range, -1*range*(GLdouble)h/(GLdouble)w,
						range*(GLdouble)h/(GLdouble)w, 0.1, 10.0);
			else
				glOrtho(-1*range*(GLdouble)w/(GLdouble)h,
						range*(GLdouble)w/(GLdouble)h, -1*range, range, 0.1, 10.0);
			break;
		case PERSPECTIVE:
			// perspective projection
			gluPerspective(60, (GLdouble)w/(GLdouble)h, 0.1, 10.0);
			break;
		default:
			fprintf( stderr, "No such projection mode!\n" );
			break;
	}

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// View Transformation
	glTranslated( 0.0, 0.0, -view_distance );

	GLdouble view_mat[16];
	for ( unsigned int i=0; i<4; i++ )
	for ( unsigned int j=0; j<4; j++ )
		view_mat[ i*4+j ] = viewing_matrix[i][j];
	glMultMatrixd( view_mat );

	// Modelling Transformation

	// drawing fixed_geo
	glPushMatrix();
	if ( fixed_geo != NULL )
	{
		col::drawGeometry( fixed_geo, fill_mode, fixed_color );
	}
	glPopMatrix();

	// drawing moving_geo
	glPushMatrix();
	GLdouble move_mat[16];
	for ( unsigned int i=0; i<4; i++ )
	for ( unsigned int j=0; j<4; j++ )
		move_mat[ i*4+j ] = moving_matrix[i][j];
	glMultMatrixd( move_mat );

	if ( moving_geo != NULL )
	{
		col::drawGeometry( moving_geo, fill_mode, moving_color );
	}
	glPopMatrix();

	// col_geo_1
	glPushMatrix();
	if ( col_geo_1 != NULL )
	{
		col::drawGeometry( col_geo_1, GL_FILL, col_color_1 );
	}
	glPopMatrix();

	// col_geo_2
	glPushMatrix();
	if ( col_geo_2 != NULL )
	{
		col::drawGeometry( col_geo_2, GL_FILL, col_color_2 );
	}
	glPopMatrix();

	//
	glutSwapBuffers();
}

void mouseClick( int button, int state, int x, int y )
{
	tball.mouseClick( button, state, x, y );
}

void mouseMoving( int x, int y )
{
	moved = true;
	tball.mouseMoving( x, y );
	glutPostRedisplay();
}


void keyboard(unsigned char key, int x, int y)
{
	static int phase = -1;

	switch (key) {
		case 27:
			exit(0);
			break;
		case ' ':
			moved = true;
			phase ++;
			testcase( &phase );

			glutPostRedisplay();
			break;
		case 't':
		{
			moved = true;
			if ( algo == col::ALGO_BOXTREE ) 
			{ 
				printf( "switched to doptree\n" );
				algo = col::ALGO_DOPTREE;
			}
			else
			if ( algo == col::ALGO_DOPTREE ) 
			{
				printf( "switched to boxtree\n" );
				algo = col::ALGO_BOXTREE;
			}
			testcase( &phase );

			glutPostRedisplay();
			break;
		}
		case 'A':
		{
			moved = true;
			if ( all_polygons == false ) 
			{ 
				printf( "show all colliding polygon pairs\n" );
				all_polygons = true;
			}
			else
			if ( all_polygons == true )
			{	
				printf( "show the first detected colliding polygon pair\n" );
				all_polygons = false;
			}
			testcase( &phase );

			glutPostRedisplay();
			break;
		}
		case 'm':
		case 'M':
			if ( ctrol == VIEWING )
			{
				printf( "switched to track-model mode\n" );
				ctrol = MODELING;
			}
			else
			if ( ctrol == MODELING )
			{
				printf( "switched to track-view mode\n" );
				ctrol = VIEWING;
			}

			glutPostRedisplay();
			break;
		case 'p':
		case 'P':
			if ( proj == PERSPECTIVE )
				proj = ORTHOGRAPHIC;
			else
			if ( proj == ORTHOGRAPHIC )
				proj = PERSPECTIVE;

			glutPostRedisplay();
			break;
		case 'i':
		case 'I':
			if ( fill_mode == GL_LINE )
				fill_mode = GL_FILL;
			else
			if ( fill_mode == GL_FILL )
				fill_mode = GL_POINT;
			else
			if ( fill_mode == GL_POINT )
				fill_mode = GL_LINE;

			glutPostRedisplay();
			break;
		default:
			break;
	}
}


/***************************************************************************\
 *                              Main                                       *
\***************************************************************************/

void commandlineerror( char *cmd, char *parm )
{
	if ( cmd )
		fprintf(stderr, "Offending option: %s\n", cmd );
	if ( parm )
		fprintf(stderr, "with first parameter: %s\n", parm );

	fprintf(stderr, "\n\nUsage: movem options ...\n"
	"Options:\n"
	"-h          show help\n"
	"Keys: (interactive version)\n"
	"i           polygon mode (wireframe, dots, filled)\n"
	"m           switch between moving the camera or the object\n"
	"space       go to the next test case\n"
	"ESC         quit\n"
	"\n");

	if ( cmd )
		exit(-1);				// problem occured
	else
		exit(0);				// was option -h
}


void parsecommandline( int argc, char *argv[] )
{
	/* valid option characters; last char MUST be 0 ! */
	char optionchar[] =   { 'h', '\0' };
	int musthaveparam[] = {  0  };
	int mhp[256];
	int isopt[256];
	char optchar;


	unsigned int nopts = strlen(optionchar);
	if ( nopts > 50 )
	{
		fprintf(stderr, "\n\nparsecommandline: the option-chars string "
				"seems to be\nVERY long (%d bytes) !\n\n", nopts );
		exit(-1);
	}

	fill_n( isopt, 256, 0 );
	fill_n( mhp, 256, 0 );
	for ( unsigned int i = 0; i < nopts; i ++ )
	{
		if ( isopt[static_cast<int>(optionchar[i])] )
		{
			fprintf(stderr, "\n\nparsecommandline: Bug: option character '%c'"
					" is specified twice in the\n"
							"option character array !\n\n", optionchar[i] );
			exit(-1);
		}
		isopt[ static_cast<int>(optionchar[i]) ] = 1;
		mhp[ static_cast<int>(optionchar[i])] = musthaveparam[i];
	}

	++argv; --argc;
	while ( argc > 0 )
	{
		if ( argv[0][0] == '-' )
		{
			optchar = argv[0][1];

			if ( ! isopt[static_cast<int>(optchar)] )
			{
				fprintf(stderr, "\nIs not a valid command line option\n");
				commandlineerror( argv[0], NULL );
			}
			for ( int i = 0; i < mhp[static_cast<int>(optchar)]; i ++ )
				if ( ! argv[1+i] || argv[1+i][0] == '-' )
				{
					fprintf(stderr, "\nCommand line option -%c must "
							"have %d parameter(s)\n",
							argv[0][1], mhp[static_cast<int>(optchar)] );
					commandlineerror( argv[0], NULL );
					argv += 1 + i;
					argc -= 1 + i;
					continue;
				}

			switch ( optchar )
			{
				case 'h': commandlineerror( NULL, NULL);  break;
				default: fprintf(stderr, "\nBug in parsecommandline !\n");
						 commandlineerror( argv[0], NULL );
			}

			argv += 1 + mhp[static_cast<int>(optchar)];
			argc -= 1 + mhp[static_cast<int>(optchar)];
		}
		else
		{
			/* command line arg doesn't start with '-' */
			fprintf(stderr, "\nThis is not a valid command line option\n");
			commandlineerror( argv[0], NULL );
			/* or, load file instead .. */
		}
	}
}

int main(int argc, char** argv)
{
	// parse command line options
	parsecommandline( argc, argv );

	// initialize global and static data
	DopTree::init();
	fixed_matrix = Matrix4::identity();
	moving_matrix = Matrix4::identity();
	viewing_matrix = Matrix4::identity();

	// prepare glut
	glutInit( &argc, argv );
	glutInitDisplayMode ( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB );
	glutInitWindowSize( 350, 350 ); 
	glutInitWindowPosition( 100, 100 );
	glutCreateWindow( argv[0] );
	init();

	// register glut callback
	glutDisplayFunc( display ); 
	glutReshapeFunc( reshape );
	glutMouseFunc( mouseClick );
	glutMotionFunc( mouseMoving );
	glutKeyboardFunc( keyboard );

	// render loop begins
	glutMainLoop();

	return 0;
}
